/*
 * utils.c
 *
 *  Created on: Dec 13, 2011
 *      Author: piotrek
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>

#include "utils.h"

#define MAX_THREAD_SIZE 20
#define MAX_RESPONSES_CNT 80

static struct monitor_thread* monitors[MAX_THREAD_SIZE];
static int threads_number = 0;
static pthread_mutex_t monitors_lock = PTHREAD_MUTEX_INITIALIZER;

void terminate_thread(pthread_t thread_id, int mqid) {
	char fname[64];
	sprintf(fname, "%s%lu", "mapped_output_", thread_id);

	if(remove(fname)) {
		perror("Error while cleanup!");
	}
	memset(fname, '\0', strlen(fname));
	sprintf(fname, "%s%lu", "maps_cnt_", thread_id);

	if (remove(fname)) {
		perror("Error while cleanup!");
	}

	memset(fname, '\0', strlen(fname));
	sprintf(fname, "%s%lu", "output_", thread_id);

	if (remove(fname)) {
		perror("Error while cleanup!");
	}

	LOG_MSG("Disposing a message queue");
	if (msgctl(mqid, IPC_RMID, NULL )) {
		perror("Cannot dispose a message queue");
	}

	LOG_MSG("After doing a cleanup");

	pthread_exit(NULL);
}


void append_monitor_thread(struct monitor_thread *thread)
{
	pthread_mutex_lock( &monitors_lock );

	monitors[threads_number] = thread;
	threads_number += 1;

	pthread_mutex_unlock( &monitors_lock );
}

void remove_monitor_thread(struct monitor_thread *thread)
{
	int i;

	pthread_mutex_lock( &monitors_lock );

	for(i=0; i<threads_number; i++)
	{
		if( monitors[i]->thread_id == thread->thread_id )
		{
			threads_number -= 1;
			break;
		}
	}

	for( ; i<threads_number; i++)
	{
		monitors[i] = monitors[i+1];
	}

	pthread_mutex_unlock( &monitors_lock );
}

struct monitor_thread* get_monitor_thread(pthread_t thread_id)
{
	int i;
	struct monitor_thread* monitor_thread = NULL;

	pthread_mutex_lock( &monitors_lock );

	for(i=0; i<threads_number; i++)
	{
		if( monitors[i]->thread_id == thread_id )
		{
			monitor_thread = monitors[i];
			break;
		}
	}

	pthread_mutex_unlock( &monitors_lock );

	return monitor_thread;
}

char* execute_script(const char *script_name, const char* target_fname, struct monitor_thread* mthread, size_t *file_len) {
	pid_t cpid;
	int status;
	char char_pid[10];
	char char_thread_id[32];
	char* buf;
	int n;

	if ((cpid = fork()) == -1) {
		perror("Fork error!\n");
	} else if (cpid == 0) { // dziecko



		sprintf(char_pid, "%d", mthread->monitoring_req.pid_to_be_monitored);
		sprintf(char_thread_id, "%lu", mthread->thread_id);


		LOG_MSG("PID to retrieve info: ");
		LOG_MSG(char_pid);

		if (execl(script_name, "_to_chyba_nie_musi_byc_nazwa_programu",
				char_pid, char_thread_id, NULL) == -1) {
			perror("Error during execl\n");
		}
	}

	// rodzic

	// czekam na dziecko
	LOG_MSG("Waiting for the child process to end ...");
	pid_t wpid;
	if ((wpid = wait(&status)) == -1) {
		perror("Wait error!\n");
	}

	if (WIFSIGNALED(status)) {
		LOG_MSG("Child process's been signaled and terminated");
	}

	LOG_MSG("About to open file: ");
	LOG_MSG(target_fname);
	FILE* f = fopen(target_fname, "rb");


	fseek(f, 0, SEEK_END);
	*file_len = ftell(f);
	rewind(f);
	buf = malloc(*file_len+1);
	n = fread(buf, 1, *file_len,f);
	buf[*file_len] = '\0';
	fclose(f);

	return buf;
}


int create_responses(int msg_id, const char *data, size_t data_size, struct monitor_response *responses) {
	int i, len;

	int msg_num = 1;

	if (data_size >= MAX_PAYLOAD) {
		msg_num = data_size % (MAX_PAYLOAD - 1) == 0 ? data_size / (MAX_PAYLOAD
				- 1) : (int) (((float) data_size / (float) (MAX_PAYLOAD - 1))
				+ 1.0);
		len = MAX_PAYLOAD - 1;
	}

	for (i = 0; i < msg_num; i++) {
		if (i == msg_num - 1)
			len = data_size - (MAX_PAYLOAD - 1) * (msg_num - 1);

		struct monitor_response *response = responses + i;
		response->mtype = 1;
		response->message_id = msg_id;
		memcpy(response->payload, data + i * (MAX_PAYLOAD - 1), len);
		response->payload[len] = '\0';
	}
	return msg_num;
}

void send_single_data(
		int queue_id,
		struct monitor_response *response,
		size_t size,
		int msgflg,
		enum thread_status *status,
		pthread_t thread_id) {

	int result = msgsnd(queue_id, response, size, msgflg);

	if (result == -1) {
		perror("Error while sending the message!\n");

		// wyswietl informacje jesli usunieto kolejke
		if(errno == EIDRM) {
			log_thread_info("The queue %lu has been removed", queue_id);
		}

		log_thread_info("Error while sending the message in thread %lu",
				thread_id);
		*status = EXIT;
	} else {
//		log_thread_info("Msg successfully sent! (Thread %lu)",
//				thread_id);
	}
}

void send_data(int queue_id, int msg_id, pthread_t thread_id, const char * data, size_t data_size) {
	int j = 0;
	struct monitor_response responses[MAX_RESPONSES_CNT];

	log_thread_info("Creating responses ... (data size %lu)", data_size);

	int response_cnt = create_responses(msg_id, data, data_size, responses);

	log_thread_info("Responses cnt %lu", response_cnt);
	log_thread_info("Message id: %lu", msg_id);

	enum thread_status  status = CONTINUE;
	for (; j < response_cnt; j++) {
		send_single_data(queue_id, responses + j, sizeof responses[j], 0, &status,
				thread_id);
		if (status == EXIT) {
			log_thread_info("Thread %lu exiting", thread_id);
			terminate_thread(thread_id, queue_id);
			break;
		}
	}

	LOG_MSG("All messages sent successfully");
}

void log_thread_info(const char *msg, unsigned long id) {
	char buff[512];
	sprintf(buff, msg, id);
	LOG_MSG(buff);
}




/**************** Pobieranie informacji odnosnie procesu ********************/


void fill_with_strace_mmaps(struct monitor_thread* mthread, char **buff, size_t *buff_pos) {
	LOG_MSG("Getting strace count");

	int sleep_interval = mthread->monitoring_req.interval * 5;

	pid_t cpid;
	int status, num_bytes;
	char output_fname[64];
	char tmp_buff[1024];
	int fd[2];

	if (pipe(fd) == -1) {
		perror("Cannot create a pipe!");
	}

	if ((cpid = fork()) == -1) {
		perror("Fork error!\n");
	} else if (cpid == 0) {

		dup2(fd[1], 2);

		LOG_MSG("In the forked process");

		char tmp_pid[10];
		sprintf(tmp_pid, "%d", mthread->monitoring_req.pid_to_be_monitored);

		if (execlp("strace", "_nazwa_", "-e", "trace=mmap,munmap,brk", "-p",
				tmp_pid, NULL) == -1) {
			perror("ERROR in execl");
			exit(-1);
		}
	}

	close(fd[1]);

	log_thread_info("Now about to sleep for (seconds) %lu ", sleep_interval);

	sleep(sleep_interval);

	LOG_MSG("Woken up");

	if (kill(cpid, 15) == -1) {
		perror("kill error\n");
	}

	if (wait(&status) == -1) {
		perror("Wait error!\n");
	}

	LOG_MSG("Writing results...");

	sprintf(output_fname, "%s%lu", "output_", mthread->thread_id);

	int out_fd = open(output_fname, O_WRONLY | O_CREAT, 0644);
	if (out_fd == -1) {
		perror("Error while opening file!");
	}
	while ((num_bytes = read(fd[0], tmp_buff, 512)) > 0) {
		if (write(out_fd, tmp_buff, num_bytes) == -1)
			perror("Error while writing!");
	}

	if (close(out_fd) == -1) {
		perror("Error while closing the file!");
	}

	LOG_MSG("Now about to read map count");



	size_t file_len;
	memset(output_fname, '\0', strlen(output_fname));
	sprintf(output_fname, "%s%lu", "mapped_output_", mthread->thread_id);
	char *content = execute_script("./daemon/Release/strace_count_memory.sh",
			output_fname, mthread, &file_len);

	// ten memcpy nam zalatwia \0 na koncu
	memcpy(*buff + *buff_pos, content, file_len);
	*buff_pos += file_len;
	free(content);
}


void fill_with_mmaps_lib_num(struct monitor_thread* mthread, char **buff, size_t *buff_pos) {
	LOG_MSG("Getting mmaps lib count");
	size_t file_len;

	char output_file[64];

	sprintf(output_file, "%s%lu", "maps_cnt_", mthread->thread_id);

	char *content = execute_script("./daemon/Release/pid_maps_lib_cnt.sh",
			output_file, mthread, &file_len);

	const char *header = "Number of mapped shared libraries: ";

	memcpy(*buff+*buff_pos, header, strlen(header));
	*buff_pos += strlen(header);

	// ten memcpy nam zalatwia \0 na koncu
	memcpy(*buff+*buff_pos, content, file_len);
	*buff_pos += file_len;
	free(content);
}

void fill_with_stat_info(struct monitor_thread* mthread, char **buff,
		size_t *buff_pos) {

	LOG_MSG("Getting stat info");

	char fname[32];
	char tmp_buff[1024];

	int fake_i;
	char fake_c;
	char fake_buf[512];
	unsigned long fake_ul;

	// prawdziwe dane
	unsigned long minflt,
				  cminflt,
				  majflt,
				  cmajflt;

	sprintf(fname, "/proc/%d/stat",
			mthread->monitoring_req.pid_to_be_monitored);

	FILE* f = fopen(fname, "r");

	if (f == NULL) {
		perror("Error while reading the file");
		return;
	}

	fake_i = fscanf(f, "%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu",
			&fake_i /* pid */,
			fake_buf /* comm */,
			&fake_c /* state */,
			&fake_i /* ppid */,
			&fake_i /* pgrp */,
			&fake_i /* session */,
			&fake_i /* terminal */,
			&fake_i /* tpgid */,
			&fake_ul /*flags*/,
			&minflt,
			&cminflt,
			&majflt,
			&cmajflt
			);

	if (fclose(f) == -1) {
		perror("Error while closing the file!");
	}

	sprintf(tmp_buff, "Errors (the number of):\n\tMinor errors (no need to load a page from the disk): %lu\n\t"
			"Minor errors of children: %lu\n\t"
			"Major errors (a page had to be loaded from HD): %lu\n\t"
			"Major errors of children: %lu\n", minflt, cminflt, majflt, cmajflt);

	memcpy(*buff + *buff_pos, tmp_buff, strlen(tmp_buff));

	*buff_pos += strlen(tmp_buff);

}

void fill_with_header(struct monitor_thread* mthread, char **buff, size_t *buff_pos) {
	LOG_MSG("Creating header");
	char tmp[512];
	sprintf(tmp ,"Tracking process of ID: %d\n", mthread->monitoring_req.pid_to_be_monitored);
	memcpy(*buff+*buff_pos, tmp, strlen(tmp));
	*buff_pos += strlen(tmp);
}

void fill_with_statm_info(struct monitor_thread* mthread, char **buff, size_t *buff_pos) {
	LOG_MSG("Getting statm info");

	char fname[32];
	char tmp_buff[1024];
	int n;

	int program_size,
	   resident,   // rozmiar części rezydentnej
	   share,      //strony wspólne
	   text,       // text (kod)
	   lib,        //     biblioteki
	   data;        //dane/stos


	sprintf(fname, "/proc/%d/statm", mthread->monitoring_req.pid_to_be_monitored);

	FILE* f = fopen(fname, "r");

	if (f == NULL) {
		perror("Error while reading the file");
		return;
	}

	n = fscanf(f, "%d %d %d %d %d %d", &program_size, &resident, &share, &text, &lib, &data);

	if (fclose(f) == -1) {
		perror("Error while closing the file!");
	}

	sprintf(tmp_buff, "The number of pages:\n\tProgram size: %d\n\tResident part size: "
			"%d\n\tCommon pages: %d\n\tCode: %d\n\tLibraries: %d\n\tData (stack): %d\n",
			program_size, resident, share, text, lib, data);

	memcpy(*buff+*buff_pos, tmp_buff, strlen(tmp_buff));

	*buff_pos += strlen(tmp_buff);

}
